using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace GameDesigner
{
    public enum BlendState
    {
        None = 0,
        CrossFadeUpWeight = 1 << 1,
        UpdateCrossFadeUpWeight = 1 << 2,
        CrossFadeDownWeight = 1 << 3,
        UpdateCrossFadeDownWeight = 1 << 4
    }

    public enum LayerBlendState
    {
        None = 0,
        EnterLayerWeight = 1 << 1,
        ExitLayerWeight = 1 << 2,
        UpdateEnterLayerWeight = 1 << 3,
        UpdateExitLayerWeight = 1 << 4,
    }

    public class PlayableStateMachine : IAnimationHandler
    {
        private Animator animator;
        private float elapsedTime;
        private float normalizedTime;
        private readonly PlayableGraph graph; //视图层
        private readonly AnimationLayerMixerPlayable animationLayerMixer; //状态机层
        private readonly AnimationMixerPlayable animationMixer; //状态机
        private readonly AnimationMixerPlayable[] animationMixers; //混合器
        private readonly StateLayer layer;
        private int animationMixerIndex;
        private float blendWeight;
        private float layerBlendWeight;
        private BlendState blendState;
        private LayerBlendState layerBlendState;

        public PlayableStateMachine(Animator animator, in PlayableGraph graph, in AnimationLayerMixerPlayable animationLayerMixer, StateLayer layer)
        {
            this.animator = animator;
            this.graph = graph;
            this.animationLayerMixer = animationLayerMixer;
            this.layer = layer;
            animationMixer = AnimationMixerPlayable.Create(graph);
            animationMixers = new AnimationMixerPlayable[2];
            animationMixers[0] = AnimationMixerPlayable.Create(graph);
            animationMixers[1] = AnimationMixerPlayable.Create(graph);
            animationMixers[0].AddInput(AnimationClipPlayable.Create(graph, null), 0, 0);
            animationMixers[1].AddInput(AnimationClipPlayable.Create(graph, null), 0, 0);
            animationMixer.AddInput(animationMixers[0], 0, 0);
            animationMixer.AddInput(animationMixers[1], 0, 0);
            animationLayerMixer.AddInput(animationMixer, 0, 0f);
            animationLayerMixer.SetInputWeight(layer.LayerIndex, layer.Weight);
        }

        public void SetParams(params object[] args) //中途修改动画对象用
        {
            animator = args[0] as Animator;
        }

        public void OnInitializeBegin(IStateMachine stateMachine)
        {
            ApplyLayerMask();
            ApplyLayerWeight(false);
        }

        public void OnInitializeEnd(IStateMachine stateMachine)
        {
        }

        public void OnStateEnter(State state)
        {
        }

        public void OnPlayAnimation(State state, StateAction stateAction)
        {
            stateAction.animTime = 0f;
            animator.speed = state.animSpeed;
            elapsedTime = 0f;
            normalizedTime = 0f;

            var animationMixerPlayable = animationMixers[animationMixerIndex % 2];
            var animationClipPlayable = (AnimationClipPlayable)animationMixerPlayable.GetInput(0);
            if (animationClipPlayable.GetAnimationClip() != stateAction.clipAsset)
            {
                var clip = stateAction.clipAsset as AnimationClip;
                if (state.isCrossFade)
                {
                    animationMixerPlayable = animationMixers[++animationMixerIndex % 2];
                    blendState = animationMixerIndex % 2 == 0 ? BlendState.CrossFadeDownWeight : BlendState.CrossFadeUpWeight;
                }
                stateAction.length = clip.length;
                animationMixerPlayable.DisconnectInput(0);
                animationClipPlayable = AnimationClipPlayable.Create(graph, clip);
                animationClipPlayable.SetSpeed(state.animSpeed);
                animationMixerPlayable.ConnectInput(0, animationClipPlayable, 0, 1.0f);
            }
        }

        public void OnAnimationEnd(State state, StateAction stateAction)
        {
        }

        public void OnStateExit(State state)
        {
        }

        public bool OnAnimationUpdate(State state, StateAction stateAction, StateMachineUpdateMode currMode)
        {
            var isPlaying = true;
            if (currMode == StateMachineUpdateMode.Update)
            {
                elapsedTime += Time.deltaTime * state.animSpeed;
                if (stateAction.length <= 0f)
                    return isPlaying; //往下执行就会变成 n/0问题
                normalizedTime = elapsedTime / stateAction.length;
                stateAction.animTime = normalizedTime / 1f * 100f;
            }
            return isPlaying;
        }

        public void ApplyLayerMask()
        {
            if (layer.AvatarMask != null)
                animationLayerMixer.SetLayerMaskFromAvatarMask((uint)layer.LayerIndex, layer.AvatarMask);
            else
                animationLayerMixer.SetLayerMaskFromAvatarMask((uint)layer.LayerIndex, layer.DefaultMask);
        }

        public void ApplyLayerWeight(bool isCrossFade = true)
        {
            if (isCrossFade)
                layerBlendState = layer.Weight > 0f ? LayerBlendState.EnterLayerWeight : LayerBlendState.ExitLayerWeight;
            else
                animationLayerMixer.SetInputWeight(layer.LayerIndex, layerBlendWeight = layer.Weight);
        }

        public void OnStateUpdate(State state, StateMachineUpdateMode currMode)
        {
            if (currMode == StateMachineUpdateMode.Update)
            {
                if (layerBlendState == LayerBlendState.EnterLayerWeight)
                    layerBlendState = LayerBlendState.UpdateEnterLayerWeight;
                else if (layerBlendState == LayerBlendState.ExitLayerWeight)
                    layerBlendState = LayerBlendState.UpdateExitLayerWeight;
                if (layerBlendState == LayerBlendState.UpdateEnterLayerWeight)
                {
                    if (layerBlendWeight < 1f)
                    {
                        layerBlendWeight += state.duration * 60f * Time.deltaTime;
                        layerBlendWeight = layerBlendWeight > 1f ? 1f : layerBlendWeight;
                    }
                    else layerBlendState = LayerBlendState.None;
                    animationLayerMixer.SetInputWeight(layer.LayerIndex, layerBlendWeight);
                }
                else if (layerBlendState == LayerBlendState.UpdateExitLayerWeight)
                {
                    if (layerBlendWeight > 0f)
                    {
                        layerBlendWeight -= state.duration * 60f * Time.deltaTime;
                        layerBlendWeight = layerBlendWeight < 0f ? 0f : layerBlendWeight;
                    }
                    else layerBlendState = LayerBlendState.None;
                    animationLayerMixer.SetInputWeight(layer.LayerIndex, layerBlendWeight);
                }

                if ((blendState & BlendState.CrossFadeUpWeight) != 0)
                {
                    blendState &= ~BlendState.CrossFadeUpWeight;
                    blendState |= BlendState.UpdateCrossFadeUpWeight;
                }
                else if ((blendState & BlendState.CrossFadeDownWeight) != 0)
                {
                    blendState &= ~BlendState.CrossFadeDownWeight;
                    blendState |= BlendState.UpdateCrossFadeDownWeight;
                }
                if ((blendState & BlendState.UpdateCrossFadeUpWeight) != 0)
                {
                    if (blendWeight < 1f)
                    {
                        blendWeight += state.duration * 60f * Time.deltaTime;
                        blendWeight = blendWeight > 1f ? 1f : blendWeight;
                    }
                    else blendState &= ~BlendState.UpdateCrossFadeUpWeight;
                    var weight1 = 1.0f - blendWeight;
                    var weight2 = blendWeight;
                    animationMixer.SetInputWeight(0, weight1);
                    animationMixer.SetInputWeight(1, weight2);
                }
                else if ((blendState & BlendState.UpdateCrossFadeDownWeight) != 0)
                {
                    if (blendWeight > 0f)
                    {
                        blendWeight -= state.duration * 60f * Time.deltaTime;
                        blendWeight = blendWeight < 0f ? 0f : blendWeight;
                    }
                    else blendState &= ~BlendState.UpdateCrossFadeDownWeight;
                    var weight1 = 1.0f - blendWeight;
                    var weight2 = blendWeight;
                    animationMixer.SetInputWeight(0, weight1);
                    animationMixer.SetInputWeight(1, weight2);
                }
            }
        }
    }
}